# encoding: utf-8
"""
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    created by lane.chang on '15/04/2024'
    comment: 底层类
"""
import json
from datetime import datetime
from decimal import Decimal
from typing import Any
from enum import Enum


import sqlalchemy.sql.schema
import sqlalchemy.types as types_
from sqlalchemy import MetaData, inspect, Integer, DateTime, Date, Time, DECIMAL, Boolean
from sqlalchemy.ext.declarative import as_declarative
from sqlalchemy import Column as _Column

from project.core.exception import BizError
from project.core.function import serializer


metadata = MetaData()


@as_declarative(metadata=metadata)
class BaseModel(object):
    # 修改剔除的列
    __edit_exclude_fields__ = ()
    # 扩展字段
    __edit_extra_fields__ = ()

    @classmethod
    def _get_columns(cls) -> list:
        """ 得到所有列
        :return:
        """
        return list(cls.__table__.columns.keys())

    @classmethod
    def field_exists(cls, filed_name) -> bool:
        """ 判断某列是否存在
        """
        return filed_name in cls._get_columns()

    def dict(self, *args, reverse: bool = False, exclude_undisplayed=True, filter_none=False) -> dict:
        """ model转换成dict(按照orm字段映射)
        :param args:
        :param reverse: 默认为False。 若为True, 则实现将args指定的字段剔除后对model进行dict化
        :param exclude_undisplayed: 过滤掉不显示字段
        :param filter_none: 是否过滤none值
        :return:
        """
        columns = set()
        _columns = self._get_columns()
        for v in dir(self):
            if v.startswith('_'):
                continue

            if not hasattr(self, v):
                continue

            # 过滤可执行对象
            if callable(getattr(self, v)):
                continue

            if isinstance(getattr(self, v), (sqlalchemy.sql.schema.MetaData, sqlalchemy.orm.decl_api.registry,)):
                continue

            if args and v not in args and not reverse:
                continue

            if args and v in args and reverse:
                continue

            if exclude_undisplayed and v in _columns and not self.__class__.__table__.columns[v].is_displayed:
                continue

            # 过滤none值(如果是对象结构[list, dict], 则判断是否有值)
            _v = getattr(self, v)
            if filter_none and (_v is None or (isinstance(_v, (list, dict)) and not _v)):
                continue

            columns.add(v)

        model_dict = {column: getattr(self, column) for column in columns}
        return model_dict

    def set_model_value(self, params=None, **kwargs):
        """设置model 的值(按照orm字段映射)
        :param params:
        :return:
        """
        params = dict() if not params else params
        params.update(kwargs)
        for k, v in params.items():
            if not hasattr(self.__class__, k):
                continue
            column = getattr(self.__class__, k)
            # 做好几种特殊的处理
            if isinstance(column, property):
                # 如果不是列, 而是属性, 这种情况是自定义额外的column, 比如密码, 就直接赋值
                setattr(self, k, v)
                continue
            if column.primary_key and (v == 0 or v == ''):
                # 如果是主键, 并且设置为0 或者为空
                setattr(self, k, v)
                continue
            if v == '' and column.nullable:
                # 如果为空, 并且该字段是可为null的, 则改为None
                setattr(self, k, None)
                continue
            if v is None and column.nullable:
                # 如果为null, 并且该字段是可为null的, 则改为None
                setattr(self, k, None)
                continue
            # 值不为空, 需要做一些类型转换
            if isinstance(column.type, Integer):
                # 数值型, 需要考虑为空的情况, 还需要考虑该字段允许不允许为空
                v = int(v) if v else 0
            elif isinstance(column.type, DECIMAL):
                # Decimal 类型, 需要考虑为空的情况, 还需要考虑该字段允许不允许为空
                v = Decimal(v) if v else 0
            elif isinstance(column.type, DateTime):
                # 日期时间(DT) 2016-1-1 9:18:10
                if isinstance(v, str):
                    v = datetime.strptime(v, '%Y-%m-%d %H:%M:%S')
                elif isinstance(v, Integer):
                    v = datetime.fromtimestamp(v)
            elif isinstance(column.type, Date):
                # 日期 2016-1-1
                if isinstance(v, str):
                    v = datetime.strptime(v, "%Y-%m-%d").date()
            elif isinstance(column.type, Time):
                # 时间: 9:18:10
                if isinstance(v, str):
                    sl = v.split(':')
                    if len(sl) == 3:
                        v = datetime.strptime(v, "%H:%M:%S").time()
                    elif len(sl) == 2:
                        v = datetime.strptime(v, "%H:%M").time()
                    elif len(sl) == 1:
                        v = datetime.strptime(v, "%H").time()
            elif isinstance(column.type, Boolean):
                # 布尔
                if not v:
                    v = False
                elif isinstance(v, str):
                    v = True if v.lower() in ('true', 't') else False
                elif isinstance(v, int):
                    v = True if v else False
                else:
                    v = False

            # 支持枚举类型
            if isinstance(v, Enum):
                self.__setattr__(k, v.value)

            self.__setattr__(k, v)

        return self


class CustomModel(BaseModel):
    """ model - 表操作维度
    """
    __abstract__ = True

    @classmethod
    def get_model(cls, session, model_id):
        """ 通过ID获得model
        :param model_id:
        :param session:
        :return:
        """
        if not model_id:
            return None
        model = session.query(cls).get(model_id)
        return model

    @classmethod
    def _query(cls, session, fields_dict=None, **kwargs):
        """ 根据条件查询
        :param session:
        :param fields_dict:
        :param kwargs:
        :return:
        """
        fields_dict = dict() if not fields_dict else fields_dict
        fields_dict.update(kwargs)

        query = session.query(cls)
        for field_name, field_value in fields_dict.items():
            query = query.filter(getattr(cls, field_name) == field_value)

        return query

    @classmethod
    def query_one(cls, session, fields_dict=None, **kwargs):
        """ 查询一条数据
        :param session:
        :param fields_dict:
        :param kwargs:
        :return:
        """
        model = cls._query(fields_dict=fields_dict, session=session, **kwargs).first()

        return model

    @classmethod
    def query_all(cls, session, fields_dict=None, order_by=None, **kwargs):
        """ 查询多条数据(支持排序)
        :param session:
        :param fields_dict:
        :param order_by:
        :param kwargs:
        :return:
        """
        query = cls._query(fields_dict=fields_dict, session=session, **kwargs)
        for v in order_by if order_by else ():
            query = query.order_by(v)
        models = query.all()

        return models

    @classmethod
    def create_modify(cls, session, model_dict=None, uniques=(), unique_err='', no_to_create=True, commit=True, **kwargs):
        """ 新增或修改(提交)
        :param session:
        :param model_dict:
        :param commit: 是否提交
        :param uniques: 唯一字段 field_name 列表, 会去验证是否重复
        :param unique_err: 是否弹出数据重复检查异常
        :param kwargs:
        :param no_to_create: 没有就新增
        :return:
        """
        pri_key = inspect(cls).primary_key[0].name
        model_dict = dict() if not model_dict else model_dict
        model_dict.update(kwargs)
        model_id = model_dict.get(pri_key)

        # 验证数据是否重复
        for unique in uniques:
            fields_dict = dict()
            for field_name in unique:
                field_value = model_dict.get(field_name)
                if field_value is None:
                    raise BizError('唯一值不能为空')
                fields_dict[field_name] = field_value

            cm = cls.query_one(session, fields_dict)
            if cm and getattr(cm, pri_key) != model_id:
                if unique_err:
                    raise BizError(unique_err)
                return cm

        # 新建修改
        if not model_id:
            if not no_to_create:
                return
            m = cls()
            session.add(m)
        else:
            m = session.query(cls).get(model_id)
            if not m:
                if not no_to_create:
                    return
                # 这里是为了兼容, 指定主键值, 比如manager之类的model, 他们的主键值由member的id决定
                m = cls()
                session.add(m)
        m.set_model_value(model_dict)

        if commit:
            session.commit()
        else:
            session.flush()

        return m

    @classmethod
    def delete_by_id(cls, session, model_id, commit=True):
        """ 删除
        :param session:
        :param model_id:
        :param commit:
        :return:
        """
        if not model_id:
            raise BizError('主键不能为空')
        key_cls = inspect(cls).primary_key[0]
        session.query(cls).filter(key_cls == model_id).delete()

        if commit:
            session.commit()
        else:
            session.flush()

    def update_self(self, session, model_dict=None, commit=True, **kwargs):
        """ 自我更新
        :param session:
        :param model_dict:
        :param commit:
        :return:
        """
        model_dict = dict() if not model_dict else model_dict
        model_dict.update(kwargs)
        self.set_model_value(model_dict)
        if commit:
            session.commit()
        else:
            session.flush()
        return self

    def delete_self(self, session, commit=True):
        """ 自我更新
        :param session:
        :param commit:
        :return:
        """
        session.delete(self)
        if commit:
            session.commit()
        else:
            session.flush()


# ==============用户自定义类型==============
class CustomList(types_.TypeDecorator):
    """ list 存储成 unicode
    """
    impl = types_.Unicode
    cache_ok = True

    def process_literal_param(self, value, dialect):
        pass

    @property
    def python_type(self):
        return ''

    def process_bind_param(self, value, dialect):
        """ set 时候调用
        :param value:
        :param dialect:
        :return:
        """
        # 这里只能处理None 和 empty, 否则会报错
        if value is None or value == '':
            return value
        if isinstance(value, str):
            return value
        elif isinstance(value, list):
            s = json.dumps(value, ensure_ascii=False)
            return s
        else:
            raise ValueError('the value type is error')

    def process_result_value(self, value, dialect):
        """ get 时候调用
        :param value:
        :param dialect:
        :return:
        """
        # 如果数据异常, 则吞掉这个错误
        value_dict = []
        try:
            value_dict = json.loads(value)
        except ValueError:
            pass
        finally:
            return value_dict


class CustomDict(types_.TypeDecorator):
    """ dict 存储成 unicode
    """

    impl = types_.VARCHAR
    cache_ok = True

    def process_literal_param(self, value, dialect):
        pass

    @property
    def python_type(self):
        return ''

    def process_bind_param(self, value, dialect):
        """ set 时候调用
        :param value:
        :param dialect:
        :return:
        """
        # 这里只能处理None 和 empty, 否则会报错
        if value is None or value == '':
            return value
        if isinstance(value, str):
            return value
        elif isinstance(value, dict):
            # json dumps 的结果是 str, 需要decode成unicode
            return json.dumps(serializer(value), ensure_ascii=False)
        else:
            raise ValueError('the value type is error')

    def process_result_value(self, value, dialect):
        """ get 时候调用
        :param value:
        :param dialect:
        :return:
        """
        # 如果数据异常, 则吞掉这个错误
        value_dict = {}
        try:
            value_dict = json.loads(value)
        except ValueError:
            pass
        finally:
            return value_dict


class Column(_Column):
    """ 扩展sqlalchemy-Column
    """
    def __init__(self, *args: Any, **kwargs: Any):
        """
        :rtype: object
        :param args:
        :param kwargs:
        """
        super(Column, self).__init__(*args, **kwargs)
        # 是否展示此属性
        self.is_displayed = kwargs.pop('is_displayed', True)

    def params(self, *optionaldict, **kwargs):
        pass

    def unique_params(self, *optionaldict, **kwargs):
        pass



class Element:
    """
    """
    def __init__(self, code, value, **kwargs):
        """
        :param code:
        :param value:
        """
        self.code = code
        self.value = value
        for k, v in kwargs.items():
            setattr(self, k, v)

    def __str__(self):
        """
        :return:
        """
        return self.code

    def dict(self):
        """
        :return:
        """
        return self.__dict__


class UEnum(Enum):

    @classmethod
    def members(cls):
        """
        :return:
        """
        return cls.__members__

    @classmethod
    def dict(cls):
        """
        :return:
        """
        return {v.name: v.value for v in cls}

    @classmethod
    def list(cls):
        """
        :return:
        """
        ret = []
        for key, ele in cls.members().items():
            model = ele.value
            if isinstance(model, Element):
                ret.append(model.dict())
                # ret.append({'code': model.code, 'value': model.value})
            else:
                ret.append(ele.value)

        return ret

    @classmethod
    def get_value(cls, code):
        """
        :param code:
        :return:
        """
        ret = ''
        for key, ele in cls.members().items():
            model = ele.value
            if isinstance(model, Element):
                if model.code == code:
                    ret = model.value
                    break
            else:
                if key == code:
                    ret = ele.value

        return ret

    @classmethod
    def enums(cls, fixed='', with_blank=False):
        """
        :return:
        """
        attrs = {k: str(v) for k, v in cls.dict().items()}
        if with_blank:
            attrs.update({'blank': ''})

        return Enum(f'{cls.__name__}Enum{fixed}', attrs)

    @classmethod
    def get_element(cls, code):
        """
        :param code:
        :return:
        """
        for key, ele in cls.members().items():
            model = ele.value
            if not isinstance(model, Element):
                return
            if model.code == code:
                return model

    @classmethod
    def get_element_value(cls, code, field_name, default=''):
        """
        :param code:
        :param field_name:
        :param default:
        :return:
        """
        ele = cls.get_element(code)
        if not ele:
            return default

        if not hasattr(ele, field_name):
            return default

        return getattr(ele, field_name, default)
